#include "mainwindow.h"
#include "ui_mainwindow.h"
#include<opencv2/imgcodecs.hpp>
#include <ui_mainwindow.h>
#include <QJsonDocument>
#include <QJsonParseError>
#include <QJsonObject>
#include <QDateTime>

//#include <QDataStream>
#include "global_config.h"

using namespace cv;


MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    //1. 初始化摄像头
    cap.open(CAP_PATH);

    //2. 初始化人脸识别器——级联表
    cas.load(HAAR_CASCADE_FILEPATH);
    //2. 开启继承定时器（并编写定时器中断函数)
    startTimer(CAP_READ_CYCLE);//100ms 启动一次

    //3. 设置重连、断网链接的信号和槽
    //connect(&m_socket, SIGNAL(QTcpSocket::Tcpconnected), this, SLOT(stop_socket_connect));
    //connect(&m_socket, SIGNAL(QTcpSocket::Tcpdisconnected), this, SLOT(restart_socket_connect));
    //connect(&m_timer, SIGNAL(QTimer::timeout()), this, SLOT(timer_socket_connect));
    connect(&m_socket, &QTcpSocket::connected, this, &MainWindow::stop_socket_connect);
    connect(&m_socket, &QTcpSocket::disconnected, this, &MainWindow::restart_socket_connect);
    connect(&m_timer, &QTimer::timeout, this, &MainWindow::timer_socket_connect);

    connect(&m_socket, &QTcpSocket::readyRead, this, &MainWindow::rcv_data);

    connect_stat = false;
    m_timer.start(CONNECT_REPEAT_CYCLE);

    m_delay_cnt = 0;
}

MainWindow::~MainWindow()
{
    delete ui;
}


/*
 * @func:定时器中断，用来显示视频的内容
*/
void MainWindow::timerEvent(QTimerEvent *event)
{
    Q_UNUSED(event);
    Mat img;
    if(cap.grab())//如果摄像头有数据
    {
        cap.read(img);
    }
    if(img.data == nullptr)//如果图片没得到数据
    {
        return;
    }

    //如果图片保存有数据
    //0. 发送给服务器（确保已经连接上服务器之后）
    #if 0
    if(connect_stat)
    {
        //qDebug() << "发送给服务器图片数据中.........";
        //send_img_to_server(img);//哪怕没有接收到人脸，也要发送过去数据

        //后改正：没接收到人间，就不发送图片
        //qDebug() << "发送完毕.........";
    }
    #endif
    //1. 将图片设置会灰度图
    Mat grey_img;
    cvtColor(img, grey_img, COLOR_BGR2GRAY);
    //2. 使用识别器进行识别
    std::vector<Rect> faces;
    cas.detectMultiScale(grey_img, faces);

    if(faces.size()>0)//如果检测到人脸了
    {
        //取出第一张人脸
        Rect first_face = faces.at(0);


#if 0//将检测到的人脸（裁剪后的）发送给客户端
        Mat croped_img = img(first_face);
        send_img_to_server(croped_img);
#else
        m_delay_cnt+=2;//有人脸的时候，50*100ms =5s发送一次。
        //没有人脸的时候，不发送
        if(m_delay_cnt >=0)//减少客户端向服务器发送数据的次数
        {
            send_img_to_server(img);
            m_delay_cnt = -100;
        }
#endif
        //在原图上画出矩形
        rectangle(img, first_face, Scalar(0,0,255),3);
        //让识别框跟随人脸移动
        //ui->recLbl->setHidden(false);
        //ui->recLbl->setGeometry(first_face.x, first_face.y, 250, 250); //我的方法
        //ui->recLbl->move(first_face.x, first_face.y);
    }
    else
    {
        ui->recLbl->setHidden(true);
    }

    //显示在屏幕上，将得到的数据做格式转换（从openCV 到 Qt 的格式）
    //1. 转换openCV格式
    cvtColor(img, img, COLOR_BGR2RGB);
    //2. cv::Mat 转换为 QImage
    QImage qtimg(img.data, img.cols, img.rows, img.step1(), QImage::Format_RGB888);
    //3. QImage 转换为 QPixmap
    QPixmap qtmap = QPixmap::fromImage(qtimg);

    ui->videoLbl->setPixmap(qtmap);
}


/*
 *@func:将图片数据发送给服务器
*/
void MainWindow::send_img_to_server(cv::Mat &img)
{
    bool ret = false;
    //0.如果没有连接上服务器，直接返回
    if(!connect_stat) return;

    //1. 将发送的图片编码，转换为vector<uchar>类型，再转换为QByteArray类型
    std::vector<uchar> img_vbuf;
    //设置im_encode 压缩参数，然后调用imencode
    int quality = 30; //压缩比率0～100
    std::vector<int> compress_params = std::vector<int>(2);
    compress_params[0] = (IMWRITE_JPEG_QUALITY);
    compress_params[1] = (quality);

    ret = imencode(".jpg",img,img_vbuf, compress_params);//关键的函数
    if(ret == false)
    {
        qDebug() << "encode JPG failed ";
        return;
    }
    QByteArray img_ba((const char*)img_vbuf.data(), img_vbuf.size());//发送数据的第二项：图片的具体数据
    quint64 img_ba_size = img_ba.size();//发送数据的第一项：图片的大小
    //qDebug() << "img's size = "<< img_ba_size;

    //2. 准备新的QByteArray，并与流绑定，通过QDataStream传入里面图片数据的大小+图片数据。
    QByteArray send_ba;//与stream绑定的ByteArray
    QDataStream stream_out(&send_ba, QIODevice::WriteOnly);//设置输出stream的数据对象
    stream_out.setVersion(QDataStream::Qt_5_12);
    stream_out<<img_ba_size<<img_ba;//stream_out << img_ba_size << send_ba;//这里错了,不应该是send_ba

    //3. 通过tcp通信将数据发送出去
    m_socket.write(send_ba);
}

/*
 * @func:在初始、断连状态下，每隔一周期，进行Tcp连接尝试
 * @desc:SLOT SIGNAL(timeout)
*/
void MainWindow::timer_socket_connect()
{
    qDebug() << "正在连接服务器";
    m_socket.connectToHost(SERVER_IP, SERVER_PORT);
}


/*
 * @func:产生断连信号后，重新尝试周期连接服务器（开启定时器)
 * @desc:SLOT SIGNAL(connected)
*/
void MainWindow::restart_socket_connect()
{
    qDebug() << "服务器连接已断开，尝试重连....";
    connect_stat = false;
    m_timer.start(CONNECT_REPEAT_CYCLE);
}

/*
 * @func:产生连接成功信号后，停止连接（关闭定时器)
 * @desc:SLOT SIGNAL(disconnected)
*/
void MainWindow::stop_socket_connect()
{
    qDebug() << "服务器连接成功";
    connect_stat = true;
    m_timer.stop();
}

/*
 * @func:从服务器获取数据
*/
void MainWindow::rcv_data()
{

#if 0 //用QDataStream 绑定socket 的方式在客户端不能正常接收数据
    QByteArray rcv_bytearr;
    QDataStream st_i(&m_socket);
    st_i.setVersion(QDataStream::Qt_5_14);

    st_i >> rcv_bytearr;
    rcv_dat.fromUtf8(rcv_bytearr);
#endif

#if 0 //调试接收，成功
    QString rcv_dat;
    rcv_dat = m_socket.readAll();
    qDebug()<< "This is rcv_dat:" <<rcv_dat;
#endif
    QByteArray rcv_bytearr;
    rcv_bytearr = m_socket.readAll();

    //解析json 数据
    //1. 创建一个 QJsonDocument 并用QByteArray初始化
    QJsonParseError err;
    QJsonDocument doc =  QJsonDocument::fromJson(rcv_bytearr, &err);
    //2. 用QJsonParseError检查是否出错
    if(err.error != QJsonParseError::NoError)
    {
        qDebug()<<"json parse error:"<<err.error;
        return;
    }
    //3. 然后通过QJsonObject得到QJsonDocument的内容，并得到键、值对。
    QJsonObject obj = doc.object();
    QString name = obj.value("name").toString();
    QString part = obj.value("part").toString();
    QString epy_id = obj.value("emy_id").toString();
    QString time = QDateTime::currentDateTime().toString();
    //QString time  = obj.value("time").toString();

    //4. 最后将得到的值显示在相应的文本输入框中
    ui->nameEdit->setText(name);
    ui->partEdit->setText(part);
    ui->noEdit->setText(epy_id);
    ui->timeEdit->setText(time);
}



